#include "globals.h"

datagram_list*
get_new_datagram_list()
{
    datagram_list* new_node = (datagram_list*) malloc(sizeof(datagram_list));
    new_node->link = 0;
    new_node->dgram = (datagram*) malloc(sizeof(datagram));
    new_node->dgram->seq_num = new_node->dgram->ack_num = 0;

    return new_node; 
}

void
traverse_datagram_buffer(
        datagram_list head
        )
{
    datagram_list* temp = head.link;

    printf("Buffer contents:\n");

    while (temp != NULL)
    {
        printf(">>Packet Info>>%d::%s::%d\n", temp->dgram->seq_num,
                temp->dgram->data, temp->dgram->ack_num);
        temp = temp->link;
    }

    printf("\n>>End of traverse>>\n");
}

void
traverse_datagram_seq(
        datagram_list head
        )
{
    datagram_list* temp = head.link;

    printf("SEQ buffered:\n");

    while (temp != NULL)
    {
        printf(">%d::", temp->dgram->seq_num);
        temp = temp->link;
    }

    printf("\n");
}

void
traverse_datagram_acks(
        datagram_list head
        )
{
    datagram_list* temp = head.link;

    printf("ACKS buffered:\n");

    while (temp != NULL)
    {
        printf(">%d::", temp->dgram->ack_num);
        temp = temp->link;
    }

    printf("\n");
}

datagram_list* 
get_last_node(
        datagram_list* head
    )
{
    datagram_list* temp = head->link;

    if (temp == NULL)
    {
        return NULL;
    }

    while (temp->link != NULL)
    {
        temp = temp->link;
    }

    return temp;
}

void 
append_buffer(
        datagram_list* head,
        datagram* dgram_
        )
{
    datagram_list* last = get_last_node(head);

    datagram_list* new_node = (datagram_list*)malloc(sizeof(datagram_list));
    new_node->dgram =  dgram_;
    new_node->link = NULL;

    if (last == NULL)
    {
       if (head->dgram->seq_num == -1)
        {
            //
            // the first ever packet is being appended to the buffer.
            //
            
            head->dgram->seq_num = 0;
            new_node->dgram->seq_num = 0; 
        }
        else
        {
            //
            // No packets in the buffer, yet we have sent feq packets already
            // and received acks for them.
            //

            new_node->dgram->seq_num = ++(head->dgram->seq_num);
        }

        head->link = new_node;
        return;
    }

    new_node->dgram->seq_num = ++(head->dgram->seq_num);
    last->link = new_node;
}

datagram_list* 
find_datagram(
        datagram_list* head,
        int num
        )
{
    datagram_list* temp = head->link;

    while (temp != NULL)
    {
        if (temp->dgram->seq_num == num)
        {
            break;
        }

        temp = temp->link;
    }

    return temp;
}

datagram* 
find_datagram_ack(
        datagram_list* head,
        int num
        )
{
    datagram_list* temp = head->link;

    while (temp != NULL)
    {
        if (temp->dgram->ack_num == num)
        {
            break;
        }

        temp = temp->link;
    }

    if (temp == NULL)
    {
        return NULL;
    }

    return temp->dgram;
}

/**
We assume we will always find the packet with the specified seq number.
*/
void 
remove_from_buffer(
        int seqno,
        datagram_list* head
        )
{
    datagram_list* temp = head->link;
    datagram_list* prev = temp;

    while (temp != NULL)
    {
        if (temp->dgram->seq_num == seqno)
        {
            //
            // Delete temp.
            //

            break;
        }

        prev = temp;
        temp = temp->link;
    }

    //
    // prev->[TBD/temp]->
    //

    if (temp == NULL)
    {
        printf("Deletion error in packet buffer\n");
      	return;
    }

    //
    // If we are deleting the first element
    //

    if (temp == prev)
    {
        head->link = temp->link;
    }
    else
    {
        prev->link = temp->link;
    }

    free(temp);

    temp = 0;
}

void 
remove_packets(
        datagram_list* head,
        int num,
        arq_params win
        )
{
    int ix = 0;

    for (ix = num; ix >= win.win_l; ix--)
    {
        remove_from_buffer(ix, head);
    }
}

int 
encode_data(
        char data_bufr[],
        int len,
        datagram* dgram
        )
{
    //
    // Return if the length is greater than expected.
    //

    if (len > MAX_DATAGRAM_BUF_SIZE)
    {
        return -1;
    }

    bzero(dgram->data, MAX_DATAGRAM_BUF_SIZE);

    if (strncpy(dgram->data, data_bufr, len) == NULL)
    {
        return -1;
    }

    return 0;
}

void 
decode_data(
        datagram dgram,
        char bufr[]
        )
{
    strcpy(bufr, dgram.data);
}

datagram *
prep_datagram(
        char dgram_bufr[]
        )
{
    int err;
    datagram *dgram = (datagram *) malloc(sizeof(datagram));

    err = encode_data(dgram_bufr, strlen(dgram_bufr), dgram);

    if (err < 0)
    {
        err_sys("Datagram encode err");
    }

    return dgram;
}

ssize_t
send_datagram(
        int sockfd,
        char* buf,
        int flags,
        datagram_list* head,
        int qflag
        )
{
    datagram* dgram;
    dgram = prep_datagram(buf);

    if (qflag)
    {
        append_buffer(head, dgram);
    }

    log_msg("before send");

    return send(sockfd, dgram, sizeof(*dgram), flags);
}

void
Sendto_datagram(
        int sockfd,
        char* buf,
        int flags,
        struct sockaddr* src_addr,
        socklen_t addrlen,
        datagram_list* head,
        int qflag
        )
{
    datagram* dgram;
    dgram = prep_datagram(buf);
     
    if (qflag)
    {
        append_buffer(head, dgram);
    }

    Sendto(sockfd, dgram, sizeof(*dgram), flags, src_addr, addrlen);
}

void
recvfrom_datagram(
        int sockfd,
        char* buf,
        int flags,
        struct sockaddr* src_addr,
        socklen_t* addrlen,
        int * rcwin
        )
{
    datagram dgram;

    recvfrom(sockfd, &dgram, sizeof(dgram), flags, 
            (SA*) src_addr, addrlen);

    *rcwin = dgram.wsize;
    decode_data(dgram, buf);
}

void
recv_datagram(
        int sockfd,
        char* buf,
        int flags
        )
{
    datagram dgram;

    recv(sockfd, &dgram, sizeof(dgram), flags);
    decode_data(dgram, buf);
}

datagram*
get_highest_in_order(
        datagram_list* head
        )
{
    datagram_list* temp = head->link;
    int num;

    //
    // Only one element in the list.
    //

    if (temp->link == NULL)
    {
        log_msg("only one element");
        return temp->dgram;
    }

    //
    // More than one element in the list.
    //

    num = temp->dgram->ack_num;
    temp = temp->link;

    for (;;num++)
    {
        if (find_datagram_ack(head, num) == NULL)
        {
            log_msg_int("Not found", num);
            break;
        }
    }

    return find_datagram_ack(head, (num-1));
}

int 
get_rand(
        int seed
        )
{
    return rand()%(seed+1);
}

int
dg_recv_datagram(
        int sockfd,
        char* buf,
        int flags,
        datagram_list* head,
        int win,
        int seed,
        float * loss_prob,
        pthread_mutex_t * buffer_mutex
        )
{
    datagram *dgram, ack, *temp;
    int dummy;
    float loss_rand;

    fd_set rdset; 
    int err;
    FD_ZERO(&rdset);
    srand (time(NULL));

    log_msg("dg_recv_datagram entry");

    while (TRUE)
    {
        dgram = (datagram *) malloc(sizeof(datagram));	 	
        FD_SET(sockfd, &rdset);
        err = select(sockfd+1, &rdset, NULL, NULL, NULL);

        if (err < 0 && errno == EINTR)
        {
            log_msg("EINTR error");
            continue;
        }
        else if (err < 0)
        {
            err_sys("Select Error %d\n", errno);
        }

        Pthread_mutex_lock(buffer_mutex);
        printf("Main thread locked the shared Buffer for writing\n");
        
        recv(sockfd, dgram, sizeof(*dgram), flags);

        loss_rand = get_rand(seed)/(seed * 1.0);

        if (loss_rand < *loss_prob)
        {
            printf("Dropping packet. Seq number: %d %f %f\n", dgram->seq_num,
                    loss_rand, *loss_prob);
	    	printf("Main thread unlocked the shared Buffer\n");
	    	Pthread_mutex_unlock(buffer_mutex);

            continue;
        }

        printf("Received Packet. Seq Number: %d %s\n", dgram->seq_num, dgram->data);

        dgram->ack_num = dgram->seq_num;
        ack.ack_num = dgram->seq_num;
        ack.seq_num = 0;
        strcpy(buf, dgram->data);

        if (find_datagram_ack(head, ack.ack_num) == NULL)
        {
            append_buffer(head, dgram); 
        }

        temp = get_highest_in_order(head);
        traverse_datagram_acks(*head);
        ack.ack_num = temp->ack_num;

        if ( temp->data[0] == EOF )
        {
            ack.seq_num = -1;
        }

        loss_rand = get_rand(seed)/(seed * 1.0);

        if (loss_rand < *loss_prob )
        {
            printf("Dropping ack. Ack number: %d %f\n", ack.ack_num, loss_rand);
       	    printf("Main thread unlocked the shared Buffer\n");	    
	    	Pthread_mutex_unlock(buffer_mutex);
            continue;
        }

        ack.wsize = win;
        printf("ACK sent: %d %d\n", ack.ack_num, ack.seq_num);
        send(sockfd, &ack, sizeof(ack), flags);

       	printf("Main thread unlocked the shared Buffer\n");	    
	    Pthread_mutex_unlock(buffer_mutex);

        if (ack.seq_num == -1)
        {
            break;
        }
    }

    return 0;
}

